<?php
// +----------------------------------------------------------------------

namespace app\admin\service;

use EasyWeChat\Kernel\Exceptions\Exception;
use think\facade\Log;

/**
 * 河南农信缴费接口类
 */
class Hnnx
{
    private $appCode; //开发者申请的appCode

    private $version = '1.0'; //接口版本

    private $apiUrl; //接口路径

    private $channelId; //渠道ID

    private $key_path; //私钥证书路径

    private $cert_path; //公钥证书路径

    private $key_pass; //证书密码

    private $charset = 'UTF-8'; // 系统编码

    private $algorithm = 'SHA256withRSA'; //加密算法

    private $deviceId; //设备ID

    private $branchId; //交易机构号

    private $MerNbr; //一级商户号

    private $notify; //支付通知地址

    protected static $instance = null; //单例化模型

    /**
     * 构造方法
     */
    public function __construct()
    {
        // 获取配置
        $config = config('hnnx');
        if ($config) {
            $this->appCode = $config['appCode'];
            $this->apiUrl = $config['apiUrl'];
            $this->channelId = $config['channelId'];
            $this->key_path = $config['key_path'];
            $this->cert_path = $config['cert_path'];
            $this->key_pass = $config['key_pass'];
            $this->deviceId = $config['deviceId'];
            $this->branchId = $config['branchId'];
            $this->MerNbr = $config['MerNbr'];
            $this->notify = $config['notify'];
        }
    }

    /**
     * 获取单例化实例对象
     * @return Hnnx object
     */
    public static function instance()
    {
        return new static();
//        if (is_null(self::$instance)) {
//            self::$instance = new static();
//        }
//        return self::$instance;
    }

    /**
     * 对参数urlencode并生成待签字符串
     * @param $params
     * @return string
     * @date 2019/9/12 11:46
     */
    protected function getSignContentUrlencode($params)
    {
        ksort($params);
        $stringToBeSigned = "";
        $i = 0;
        foreach ($params as $k => $v) {
            if ("@" != substr($v, 0, 1)) {

                // 转换成目标字符集
                $v = $this->characet($v, $this->charset);

                if ($i == 0) {
                    $stringToBeSigned .= "$k" . "=" . urlencode($v);
                } else {
                    $stringToBeSigned .= "&" . "$k" . "=" . urlencode($v);
                }
                $i++;
            }
        }

        unset ($k, $v);
        return $stringToBeSigned;
    }

    /**
     * 生成签名
     * @param $params
     * @param string $data 待签名字符串
     * @return string
     * @date 2019/9/21 16:39
     */
    public function sign($params, $data = '')
    {
        if ($data === '') {
            $data = $this->getSignContentUrlencode($params);
        }
        // 获取证书私钥
        try{
            $priKey = file_get_contents($this->key_path);
        } catch (\Exception $e) {
            api_error('私钥证书配置错误');
        }

        $res = openssl_get_privatekey($priKey, $this->key_pass);

        ($res) or die('您使用的私钥格式错误，请检查RSA私钥配置');
        //SHA256withRSA签名
        openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256);

        if (!$this->checkEmpty($this->key_path)) {
            openssl_free_key($res);
        }
        return bin2hex($sign);
    }

    /**
     * 校验$value是否非空
     *  if not set ,return true;
     *    if is null , return true;
     **/
    protected function checkEmpty($value)
    {
        if (!isset($value))
            return true;
        if ($value === null)
            return true;
        if (trim($value) === "")
            return true;

        return false;
    }

    /**
     * 转换字符集编码
     * @param $data
     * @param $targetCharset
     * @return string
     */
    protected function characet($data, $targetCharset)
    {

        if (!empty($data)) {
            $fileType = $this->charset;
            if (strcasecmp($fileType, $targetCharset) != 0) {
                $data = mb_convert_encoding($data, $targetCharset, $fileType);
            }
        }


        return $data;
    }

    /**
     * 创建请求参数
     * @return array
     * @date 2019/9/21 17:13
     */
    public function creatParam()
    {
        $param = [
            'appCode' => $this->appCode,
            'version' => $this->version,
            'channelId' => $this->channelId,
            'charset' => $this->charset,
            'algorithm' => $this->algorithm,
            'deviceId' => $this->deviceId,
            'branchId' => $this->branchId,
        ];
        //请求序列号【渠道端产生，一笔业务的唯一标识，贯穿整个交易始终】渠道代码（2位）+日期（yyMMdd 6位）+流水号（24位）
        $param['sequenceId'] = $this->channelId . date('ymd') . substr($this->appCode . date('His') . rand(10000000, 99999999), 0, 24);
        //交易时间戳:yyyyMMddHHmmssSSS
        list($msec, $sec) = explode(' ', microtime());
        $param['timestamp'] = date("YmdHis") . round(floatval($msec) * 1000, 0);
        return $param;
    }

    /**
     * 处理返回结果
     * @param $res
     * @return array
     * @date 2019/9/23 17:01
     */
    public function handle($res)
    {
        $sign_check = $this->checkResponseSign($res);
        cache('hnnx_return', $res);
        if (!$sign_check) {
            return ['code' => 2, 'msg' => '验签失败！'];
        }
        if ($res['code'] != '000000') {
            return ['code' => 0, 'msg' => $res['message'], 'data' => $res];
        }
        return ['code' => 1, 'msg' => $res['message'], 'data' => json_decode($res['payload'], true)];
    }

    /**
     * curl请求
     * @param $headers header请求头
     * @param $param 参数（signature,payload）
     * @return bool|string json串
     * @throws Exception
     * @date 2019/9/21 18:32
     */
    public function curl($headers, $param)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->apiUrl);
        curl_setopt($ch, CURLOPT_FAILONERROR, false);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

        $postBodyString = "";
        $encodeArray = array();
        $postMultipart = false;
        if (is_array($param) && 0 < count($param)) {

            foreach ($param as $k => $v) {
                if ("@" != substr($v, 0, 1)) //判断是不是文件上传
                {

                    $postBodyString .= "$k=" . urlencode($this->characet($v, $this->charset)) . "&";
                    $encodeArray[$k] = $this->characet($v, $this->charset);
                } else //文件上传用multipart/form-data，否则用www-form-urlencoded
                {
                    $postMultipart = true;
                    $encodeArray[$k] = new \CURLFile(substr($v, 1));
                }

            }
            unset ($k, $v);
            curl_setopt($ch, CURLOPT_POST, true);
            if ($postMultipart) {
                curl_setopt($ch, CURLOPT_POSTFIELDS, $encodeArray);
            } else {
                $postBodyString = trim($postBodyString, "&");
                curl_setopt($ch, CURLOPT_POSTFIELDS, $postBodyString);
            }
        }
        parse_str($postBodyString, $param); //缓存请求
        cache('hnnx_param', $param);
//        trace(json_encode($param));
        Log::write(json_encode($param));
        if ($postMultipart) {
            $headers = array('content-type: multipart/form-data;charset=' . $this->charset . ';boundary=' . $this->getMillisecond());
        } else {
            $headers["Content-Type"] = 'application/x-www-form-urlencoded';
        }
        foreach ($headers as $k => $v) {
            $new_heasers[] = "{$k}:{$v}";
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $new_heasers);
        $reponse = curl_exec($ch);
        if (curl_errno($ch)) {
            throw new Exception(curl_error($ch), 0);
        } else {
            $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if (200 !== $httpStatusCode) {
                throw new Exception($reponse, $httpStatusCode);
            }
        }
        curl_close($ch);
        return $reponse;
    }

    /**
     * 拼接验签字段
     * @param $res
     * @return int
     * @date 2019/9/23 16:59
     */
    protected function checkResponseSign($res)
    {
        $plainMap = $res;
        unset($plainMap['payload']);
        unset($plainMap['signature']);
        if ($res['payload']) {
            $payload = json_decode($res['payload'], true);
            $plainMap = array_merge($plainMap, $payload);
        }
        $str_sign = $this->getSignContentUrlencode($plainMap);
        return $this->rsaEncrypt($str_sign, $res['signature']);
    }

    /**
     * 验签
     * @param $original_str
     * @param $sign
     * @return int
     * @date 2019/9/23 16:59
     */
    public function rsaEncrypt($original_str, $sign)
    {
        $pubKey = file_get_contents($this->cert_path);
        $res = openssl_get_publickey($pubKey);
        return openssl_verify($original_str, hex2bin($sign), $res, OPENSSL_ALGO_SHA256);
    }

    /**
     * 生成订单
     * @param $original_str
     * @param $sign
     * @return int
     * @author 迅 paulxun@qq.com
     * @date 2020/3/12 09:31
     */
    public function order($data = [])
    {
        $header = $this->creatParam();
        $payload['CurrencyCd'] = 'CNY';
        $payload['TransAmt'] = $data['TransAmt']; //交易金额
        $payload['MerNbr'] = $this->MerNbr; //商户编号
        $payload['MerTransDateTime'] = date("YmdHis"); //交易时间
        $payload['ChannelNbr'] = '02'; //渠道编号
        $payload['MerSeqNbr'] = date("YmdHis") . rand(100, 999); //商户流水号
        $payload['OrderNbr'] = $payload['MerSeqNbr'] . rand(1, 9); //订单编号
        $payload['SubMerchantId'] = $data['SubMerchantId']; //子商户号
        $sign_param = array_merge($header, $payload);
        $sign = $this->sign($sign_param);
        $param['payload'] = json_encode($payload);
        $param['signature'] = $sign;
        $header['transCode'] = 'hnnx.trade.sandbox.upp.addorderinfo';
        $res = json_decode($this->curl($header, $param), true);
        return $this->handle($res);
    }

    /**
     * 用户主动扫码生成
     * @param $data
     * @return array
     * @throws Exception
     * @date 2019/9/23 20:48
     */
    public function dyActiveQrCode($data)
    {
        $header = $this->creatParam();
        $payload['MerNbr'] = $this->MerNbr; //商户编号
        $payload = array_merge($payload, $data);
        $sign_param = array_merge($header, $payload);
        $sign = $this->sign($sign_param);
        $param['payload'] = json_encode($payload);
        $param['signature'] = $sign;
        $header['transCode'] = 'hnnx.trade.sandbox.upp.dyActiveQrCode';
        $res = json_decode($this->curl($header, $param), true);
        return $this->handle($res);
    }

    /**
     * 用户被扫支付
     * @param $data
     * @return array
     * @throws Exception
     * @date 2019/9/23 20:34
     */
    public function coretoall($data)
    {
        $header = $this->creatParam();
        $payload['MerNbr'] = $this->MerNbr; //商户编号
        $payload['MerSeqNbr'] = date("YmdHis") . rand(100, 999); //商户流水号
        $payload['TransAmt'] = $data['money']; //交易金额
        $payload['MerTransDateTime'] = date("YmdHis"); //交易时间
        $payload['QrCodeInfo'] = $data['qrcode']; //二维码信息
        $payload['ChannelNbr'] = '03'; //渠道编号
        $payload['CurrencyCd'] = 'CNY'; //人民币
        $payload['AreaInfo'] = '1564988'; //地区
        $payload['SubMerchantId'] = 'AA1905050000819'; //二级商户
        $payload['OrderTitle'] = '测试'; //二级商户
        $payload['MerUrl'] = $this->notify; //支付通知
        $sign_param = array_merge($header, $payload);
        $sign = $this->sign($sign_param);
        $param['payload'] = json_encode($payload);
        $param['signature'] = $sign;
        $header['transCode'] = 'hnnx.trade.sandbox.upp.coretoall';
        $res = json_decode($this->curl($header, $param), true);
        return $this->handle($res);
    }

    /**
     * 通用退款
     * @param $data 原金额、子商户号、原流水、原交易日期
     * @return array
     * @author 迅 paulxun@qq.com
     * @date 2020/3/12 10:31
     */
    public function tuikuan($data = [])
    {
        $header = $this->creatParam();
        $payload['MerNbr'] = $this->MerNbr; //商户编号
        $payload['MerSeqNbr'] = date("YmdHis") . rand(100, 999); //商户流水号
        $payload['MerTransDateTime'] = date("YmdHis"); //商户时间戳
        $payload['SubMerSeqNo'] = $payload['MerSeqNbr']; //二级商户流水号 和一级一致
        $payload['SubMerDateTime'] = $payload['MerTransDateTime']; //二级商户退货交易时间

        $payload['OrigMerSeqNbr'] = $data['OrigMerSeqNbr']; //原商户支付流水号
        $payload['OrigMerDate'] = $data['OrigMerDate']; //原商户支付交易日期

        $payload['OrigTransAmt'] = $data['OrigTransAmt']; //原支付交易金额
        $payload['SubMerchantId'] = $data['SubMerchantId']; //二级商户编号
        $payload['SubTransAmt'] = $payload['OrigTransAmt']; //原二级商户金额
        $payload['OrigSubMerSeqNo'] = $payload['OrigMerSeqNbr']; //原二级商户支付流水号
        $payload['OrigSubMerDate'] = $payload['OrigMerDate']; //原二级商户支付交易日期
        $payload['OrderNbr'] = $payload['SubMerSeqNo']; //订单号 与二级商户流水号保持一致
        $sign_param = array_merge($header, $payload);
        $sign = $this->sign($sign_param);
        $param['payload'] = json_encode($payload);
        $param['signature'] = $sign;
        $header['transCode'] = 'hnnx.trade.sandbox.upp.returntrans';
        $res = json_decode($this->curl($header, $param), true);
        return $this->handle($res);
    }

    /**
     *订单交易状态查询
     * @param $MerSeqNbr 订单流水号
     * @author 迅 paulxun@qq.com
     * @date 2020/3/12 10:31
     */
    public function qryQrOrderStatus($MerSeqNbr)
    {
        $header = $this->creatParam();
        $payload['MerNbr'] = $this->MerNbr; //商户编号
        $payload['MerSeqNbr'] = $MerSeqNbr;
        $sign_param = array_merge($header, $payload);
        $sign = $this->sign($sign_param);
        $param['payload'] = json_encode($payload);
        $param['signature'] = $sign;
        $header['transCode'] = 'hnnx.trade.sandbox.upp.qryQrOrderStatus';
        $res = json_decode($this->curl($header, $param), true);
        return $this->handle($res);
    }

    /**
     * 预支付
     * @param $data 金额、子商户号、授权码
     * @return array
     * @author 迅 paulxun@qq.com
     * @date 2020/3/12 10:31
     */
    public function PrePay($data = [])
    {
        $header = $this->creatParam();
        $payload['MerNbr'] = $this->MerNbr; //商户编号
        $payload['SubMerchantId'] = $data['SubMerchantId']; //二级商户编号
        $payload['MerTransDateTime'] = date("YmdHis"); //交易时间
        $payload['TransAmt'] = $data['money']; //交易金额
        $payload['AuthCode'] = $data['AuthCode']; //微信支付宝授权码
        $payload['ChannelNbr'] = '03'; //渠道编号
        $payload['PayTypCd'] = $data['PayTypCd']; //线下扫码
        $payload['OrderTitle'] = $data['OrderTitle']; //订单标题
        $payload['CurrencyCd'] = 'CNY'; //人民币
        $payload['MerUrl'] = $this->notify; //通知URL
        $payload['OperatorId'] = $data['OperatorId']; //操作员
//		$payload['MerSeqNbr'] = date("YmdHis").rand(100,999); //商户订单号
        $payload['MerSeqNbr'] = $data['MerSeqNbr'] ?? date("YmdHis") . rand(100, 999); //商户订单号
//		cookie('order', $payload['MerSeqNbr']);
        $sign_param = array_merge($header, $payload);
//        api_error(json_encode($sign_param['payload']));
//        api_error('aaa');
        $sign = $this->sign($sign_param);
        $param['payload'] = json_encode($payload);
        $param['signature'] = $sign;
        $header['transCode'] = 'hnnx.trade.sandbox.upp.qrCodePrePay';
        $res = json_decode($this->curl($header, $param), true);
        Log::write(json_encode($res), 'PrePay');
        return $this->handle($res);
    }

    /**
     * 支付通知验签
     * @param $data
     * @return array
     * @author 迅 paulxun@qq.com
     * @date 2020/3/12 10:31
     */
    public function notify($data = null)
    {
        cache('hnnx_notify', $data);
        if ($data == null) {
            return 0;
        }
        if (!isset($data['signature'])) return 0;
        $data['payload'] = null;
        $return = $this->checkResponseSign($data);
        return $return;
    }

}
